Please feel free to contribute to the Github repo here. You can start by cloning this repository or contacting me at my email to be added as a collaborator. Could certainly use your help!
knitr::opts_chunk$set(
error = FALSE,
message = FALSE,
warning = FALSE
)
library(tidyverse)
library(rvest)
library(magrittr)
library(lubridate)
library(plotly)
Understanding our data sources
John Hopkins CSSE
Daily confirmed cases, deaths and recoveries broken down by country/region and state/province, when state/province is available.
confirmed_ts <- read_csv(url("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_19-covid-Confirmed.csv")) %>%
gather(date, confirmed, -`Province/State`, -`Country/Region`, -Lat, -Long) %>%
mutate(date = mdy(date))
deaths_ts <- read_csv(url("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_19-covid-Deaths.csv")) %>%
gather(date, deaths, -`Province/State`, -`Country/Region`, -Lat, -Long) %>%
mutate(date = mdy(date))
recovered_ts <- read_csv(url("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_19-covid-Recovered.csv")) %>%
gather(date, recovered, -`Province/State`, -`Country/Region`, -Lat, -Long) %>%
mutate(date = mdy(date))
joined <- confirmed_ts %>%
left_join(deaths_ts) %>%
left_join(recovered_ts) %>%
gather(stat, value, confirmed, deaths, recovered) %>%
arrange(`Country/Region`, `Province/State`, date, stat)
joined %>% mutate_if(is.character, as.factor) %>% summary()
Province/State Country/Region Lat Long date stat
Diamond Princess: 330 US :40755 Min. :-41.45 Min. :-157.86 Min. :2020-01-22 confirmed:25465
Grand Princess : 330 China : 5445 1st Qu.: 27.61 1st Qu.: -93.06 1st Qu.:2020-02-04 deaths :25465
Adams, IN : 165 Canada : 1815 Median : 37.81 Median : -74.30 Median :2020-02-18 recovered:25465
Alabama : 165 Australia : 1485 Mean : 31.81 Mean : -36.13 Mean :2020-02-18
Alachua, FL : 165 France : 1155 3rd Qu.: 42.33 3rd Qu.: 20.94 3rd Qu.:2020-03-03
(Other) :50985 United Kingdom: 660 Max. : 71.71 Max. : 174.89 Max. :2020-03-16
NA's :24255 (Other) :25080
value
Min. : 0.0
1st Qu.: 0.0
Median : 0.0
Mean : 66.8
3rd Qu.: 0.0
Max. :67798.0
#' Prepares data.frame for plotting by aggregating, weighting, reordering,
#' and filtering by location.
#'
#' @param geo_level column name to aggregate (sum) by
#' @param dcr_wts weights to order locations; vector of c(deaths, confirmed, recovered)
#' @param show_top_n show top n locations ordered by dcr_wts
#'
#' @return an aggregated, reordered, and filtered by location data.frame
#'
#' @examples
#' plot_prep("Country/Region", c(100, 1, -1), show_top_n = 25)
#'
#' @export
plot_prep <- function(df, geo_level = "Country/Region", dcr_wts = c(1, 0.01, 0),
show_top = 1:10) {
df %>%
mutate(location = !!sym(geo_level)) %>%
# aggregate by location
group_by(location, date, stat) %>%
summarise(value = sum(value)) %>%
ungroup() %>%
# location order
group_by(location) %>%
mutate(
total_wt =
value[which(date == max(date) & stat == "deaths")] * dcr_wts[1] +
value[which(date == max(date) & stat == "confirmed")] * dcr_wts[2] +
value[which(date == max(date) & stat == "recovered")] * dcr_wts[3]
) %>%
ungroup() %>%
mutate(total_rank = dense_rank(desc(total_wt))) %>%
# filter top n
filter(total_rank %in% show_top) %>%
# order by rank
mutate(location = fct_reorder(location, total_rank))
}
Cross-country comparison
#' Convenience function to plot country comparisons
plot_country_comps <- function(df) {
df %>%
ggplot(aes(date, value, color=location)) +
geom_line() + facet_wrap(vars(stat), ncol=1, scales = "free_y") +
theme(legend.position = "right", legend.title = element_blank()) +
xlab(element_blank())
}
Top countries
joined %>%
plot_prep("Country/Region") %>%
plot_country_comps()

excl China
joined %>%
filter(!`Country/Region` %in% c("China")) %>%
plot_prep("Country/Region") %>%
plot_country_comps()

excl Italy, Iran, South Korea
joined %>%
filter(!`Country/Region` %in% c("China", "Italy", "Iran", "Korea, South")) %>%
plot_prep("Country/Region") %>%
plot_country_comps()

excl Spain, France
joined %>%
filter(!`Country/Region` %in% c("China", "Italy", "Iran", "Korea, South",
"Spain", "France")) %>%
plot_prep("Country/Region") %>%
plot_country_comps()

Comparing growth
#' Convenience function to plot growth comparisons
plot_growth_comps <- function(df) {
df %>%
ggplot(aes(date, value)) + geom_line() +
theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)) +
facet_wrap(
vars(location, stat), scales="free_y", ncol = 3,
labeller=label_wrap_gen(multi_line=FALSE)) +
theme(strip.background = element_blank(), legend.position = "None") +
xlab(element_blank())
}
By country
joined %>%
plot_prep() %>%
plot_growth_comps()

China
joined %>%
filter(`Country/Region` == "China") %>%
plot_prep("Province/State") %>%
plot_growth_comps()

US
joined %>%
filter(`Country/Region` == "US") %>%
plot_prep("Province/State") %>%
plot_growth_comps()

Canada
joined %>%
filter(`Country/Region` == "Canada") %>%
plot_prep("Province/State") %>%
plot_growth_comps()

Australia
joined %>%
filter(`Country/Region` == "Australia") %>%
plot_prep("Province/State") %>%
plot_growth_comps()

Total deaths since first death
plot_deaths_since_first <- function(df, days_since, init_deaths = 3,
show_top = 1:10) {
xlab <- paste("Days since deaths =", init_deaths)
df %>%
plot_prep("Country/Region", show_top = show_top) %>%
group_by(location) %>%
mutate(total_deaths = value[which(date == max(date) &
stat == "deaths")][1]) %>%
filter(total_deaths > 0) %>%
mutate(init_death_date = date[which(value >= init_deaths &
stat == "deaths")][1]) %>%
mutate(
!!sym(xlab) := date - init_death_date) %>%
filter(stat == "deaths" & value > 0 &
between(!!sym(xlab), 0, days_since)) %>%
ggplot(aes(!!sym(xlab), value,
color=location)) + geom_line() +
theme(strip.background = element_blank(), legend.position = "right",
legend.title = element_blank()) +
ylab("Total deaths")
}
Dashboard version
10 days since first death
joined %>%
plot_deaths_since_first(10)

30 days since first death
joined %>%
plot_deaths_since_first(30)

60 days since first death
joined %>%
plot_deaths_since_first(60)

Double days
Double days by country/stat
Top 10 countries
joined %>%
plot_prep(dcr_wts = c(100, 0.1, 0), show_top = 1:10) %>%
group_by(location, stat) %>%
arrange(date) %>%
mutate(
`Days to double` = if_else(
value < 10, as.difftime("", units="days"),
date - date[sapply(value, function(x) { which.min(abs(x / 2 - value)) })])
) %>%
ggplot(aes(date, `Days to double`, color=stat)) +
geom_point(alpha = 0.5) +
geom_smooth() +
#facet_wrap(vars(location), ncol = 3) +
facet_grid(vars(location), vars(stat), scales="free") +
theme(legend.position = "bottom", legend.title=element_blank()) +
xlab(element_blank())

excl China
joined %>%
filter(`Country/Region` != "China") %>%
plot_prep(dcr_wts = c(100, 0.1, 0), show_top = 1:10) %>%
group_by(location, stat) %>%
arrange(date) %>%
mutate(
`Days to double` = if_else(
value < 10, as.difftime("", units="days"),
date - date[sapply(value, function(x) { which.min(abs(x / 2 - value)) })])
) %>%
ggplot(aes(date, `Days to double`, color=stat)) +
geom_point(alpha = 0.5) +
geom_smooth() +
#facet_wrap(vars(location), ncol = 3) +
facet_grid(vars(location), vars(stat), scales="free") +
theme(legend.position = "bottom", legend.title=element_blank()) +
xlab(element_blank())

Days to double deaths since nth death
h/t: @loeserjohn
Dashboard version
Country/Region level
n_deaths <- 10
min_deaths <- n_deaths * 2
top_n_countries <- 1:20
trunc_to_n <- 2
geo_level = "Country/Region"
# TODO(roboton): Refactor into prep and plot functions
joined %>%
# plot prep at country level
plot_prep(geo_level, show_top = top_n_countries) %>%
# limit to deaths
filter(stat == "deaths") %>%
# drop countries with less than min_deaths
group_by(location) %>%
filter(value[which.max(date)] >= min_deaths) %>%
ungroup() %>%
# get the first date with more than or equal to n_deaths
group_by(location) %>%
mutate(nth_death_date = date[which(value >= n_deaths) - 1][1]) %>%
mutate(`Days since nth death` = date - nth_death_date) %>%
# Drop days before nth deaths date
filter(`Days since nth death` > 0) %>%
# Compute Days to Double
group_by(location) %>%
mutate(
# set days to double
double_idx = sapply(value, FUN=function(x) { max(which(value <= x/2)) }),
`Days to double` = date - date[double_idx]
) %>%
filter(!is.na(`Days to double`)) %>%
## plot
# truncate to second longest time series
group_by(location) %>%
mutate(max_days_since_nth_death = max(`Days since nth death`)) %>%
ungroup() %>%
mutate(max_days_rank = dense_rank(desc(max_days_since_nth_death))) %>%
filter(`Days since nth death` <= max(`Days since nth death`[max_days_rank == trunc_to_n])) %>%
group_by(location) %>%
mutate(time_points = n()) %>%
ungroup() %>%
filter(time_points > 2) %>%
ggplot(aes(`Days since nth death`, `Days to double`, color=location)) +
geom_line(stat = "smooth", method = "auto", se = F) +
geom_point(alpha = 0.2, size = 1) +
xlab(paste("days since deaths =", n_deaths)) -> p
ggplotly(p)
Long view
n_deaths <- 10
min_deaths <- n_deaths * 2
top_n_countries <- 1:15
trunc_to_n <- 1
geo_level = "Country/Region"
# TODO(roboton): Refactor into prep and plot functions
joined %>%
#filter(!is.na(`Province/State`) & `Province/State` != `Country/Region`) %>%
# plot prep at country level
plot_prep(geo_level, show_top = top_n_countries) %>%
# limit to deaths
filter(stat == "deaths") %>%
# drop countries with less than min_deaths
group_by(location) %>%
filter(value[which.max(date)] >= min_deaths) %>%
ungroup() %>%
# get the first date with more than or equal to n_deaths
group_by(location) %>%
mutate(nth_death_date = date[which(value >= n_deaths) - 1][1]) %>%
mutate(`Days since nth death` = date - nth_death_date) %>%
# Drop days before nth deaths date
filter(`Days since nth death` > 0) %>%
# Compute Days to Double
group_by(location) %>%
mutate(
# set days to double
double_idx = sapply(value, FUN = function(x) { max(which(value <= x/2)) }),
`Days to double` = date - date[double_idx]
) %>%
filter(!is.na(`Days to double`)) %>%
## plot
# truncate to second longest time series
group_by(location) %>%
mutate(max_days_since_nth_death = max(`Days since nth death`)) %>%
ungroup() %>%
mutate(max_days_rank = dense_rank(desc(max_days_since_nth_death))) %>%
filter(`Days since nth death` <= max(`Days since nth death`[max_days_rank == trunc_to_n])) %>%
group_by(location) %>%
mutate(time_points = n()) %>%
ungroup() %>%
filter(time_points > 2) %>%
ggplot(aes(`Days since nth death`, `Days to double`,
color = location)) +
geom_line(stat = "smooth", method = "auto", se = F) +
geom_point(alpha = 0.2, size = 1) +
xlab(paste("days since deaths >=", n_deaths)) -> p
ggplotly(p)
Prototype
min_total <- 10
min_stat <- "deaths"
geo_level <- "Country/Region"
max_days_since <- 20
min_total <- 50
joined %>%
rename(total = value) %>%
mutate(location = !!sym(geo_level)) %>%
# aggregate
group_by(location, stat, date) %>%
summarise(total = sum(total)) %>%
# add cfr
bind_rows(
(.) %>%
select(location, stat, date, total) %>%
spread(stat, total) %>%
mutate(cfr = deaths/confirmed) %>%
gather(stat, total, confirmed, deaths, recovered, cfr) %>%
filter(stat == "cfr")) %>%
# get max_total and first_date per location/stat
group_by(location, stat) %>%
mutate(max_total = max(total),
first_date = min(date[total >= min_total])) %>%
group_by(location) %>%
# drop earlier dates
filter(date >= first_date[stat == min_stat]) %>%
# recenter dates
mutate(days_since = date - first_date[stat == min_stat]) %>%
ungroup() %>%
# calc double_days
group_by(location, stat) %>%
mutate(
double_days = date -
date[sapply(
total, FUN = function(x) { max(which(total <= x/2)) })]) %>%
ungroup() %>%
gather(value_type, value, total, double_days) %>%
## plotting
# truncate days_since
filter(days_since <= max_days_since) %>%
# dropping small locations
group_by(location) %>%
filter(max_total[stat == min_stat][1] >= min_total) %>%
ungroup() %>%
ggplot(aes(days_since, value, color = location)) +
geom_line() +
facet_wrap(vars(stat, value_type), scales = "free", ncol=2,
labeller = label_wrap_gen(multi_line = FALSE))

LS0tCnRpdGxlOiAiQ09WSUQtMTkgRXhwbG9yYXRvcnkiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0aGVtZTogbHVtZW4KICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKUGxlYXNlIGZlZWwgZnJlZSB0byBjb250cmlidXRlIHRvIHRoZSBbR2l0aHViIHJlcG9dKGh0dHBzOi8vZ2l0aHViLmNvbS9yb2JvdG9uL2NvdmlkLTE5X21ldGEpIGhlcmUuICBZb3UgY2FuIHN0YXJ0IGJ5IFtjbG9uaW5nXShodHRwczovL2hlbHAuZ2l0aHViLmNvbS9lbi9naXRodWIvY3JlYXRpbmctY2xvbmluZy1hbmQtYXJjaGl2aW5nLXJlcG9zaXRvcmllcy9jbG9uaW5nLWEtcmVwb3NpdG9yeSkgdGhpcyByZXBvc2l0b3J5IG9yIGNvbnRhY3RpbmcgbWUgYXQgbXkgW2VtYWlsXShtYWlsdG86cm9iZXJ0b25AZ21haWwuY29tKSB0byBiZSBhZGRlZCBhcyBhIGNvbGxhYm9yYXRvci4gIENvdWxkIGNlcnRhaW5seSB1c2UgeW91ciBoZWxwIQoKYGBge3Igc2V0dXB9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKCWVycm9yID0gRkFMU0UsCgltZXNzYWdlID0gRkFMU0UsCgl3YXJuaW5nID0gRkFMU0UKKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShydmVzdCkKbGlicmFyeShtYWdyaXR0cikKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkocGxvdGx5KQpgYGAKCiMgVW5kZXJzdGFuZGluZyBvdXIgZGF0YSBzb3VyY2VzCgojIyBKb2huIEhvcGtpbnMgQ1NTRQoKRGFpbHkgY29uZmlybWVkIGNhc2VzLCBkZWF0aHMgYW5kIHJlY292ZXJpZXMgYnJva2VuIGRvd24gYnkgY291bnRyeS9yZWdpb24gYW5kIHN0YXRlL3Byb3ZpbmNlLCB3aGVuIHN0YXRlL3Byb3ZpbmNlIGlzIGF2YWlsYWJsZS4KCmBgYHtyfQpjb25maXJtZWRfdHMgPC0gcmVhZF9jc3YodXJsKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQ1NTRUdJU2FuZERhdGEvQ09WSUQtMTkvbWFzdGVyL2Nzc2VfY292aWRfMTlfZGF0YS9jc3NlX2NvdmlkXzE5X3RpbWVfc2VyaWVzL3RpbWVfc2VyaWVzXzE5LWNvdmlkLUNvbmZpcm1lZC5jc3YiKSkgJT4lCiAgZ2F0aGVyKGRhdGUsIGNvbmZpcm1lZCwgLWBQcm92aW5jZS9TdGF0ZWAsIC1gQ291bnRyeS9SZWdpb25gLCAtTGF0LCAtTG9uZykgJT4lCiAgbXV0YXRlKGRhdGUgPSBtZHkoZGF0ZSkpCmRlYXRoc190cyA8LSByZWFkX2Nzdih1cmwoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9DU1NFR0lTYW5kRGF0YS9DT1ZJRC0xOS9tYXN0ZXIvY3NzZV9jb3ZpZF8xOV9kYXRhL2Nzc2VfY292aWRfMTlfdGltZV9zZXJpZXMvdGltZV9zZXJpZXNfMTktY292aWQtRGVhdGhzLmNzdiIpKSAlPiUKICBnYXRoZXIoZGF0ZSwgZGVhdGhzLCAtYFByb3ZpbmNlL1N0YXRlYCwgLWBDb3VudHJ5L1JlZ2lvbmAsIC1MYXQsIC1Mb25nKSAlPiUKICBtdXRhdGUoZGF0ZSA9IG1keShkYXRlKSkKcmVjb3ZlcmVkX3RzIDwtIHJlYWRfY3N2KHVybCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0NTU0VHSVNhbmREYXRhL0NPVklELTE5L21hc3Rlci9jc3NlX2NvdmlkXzE5X2RhdGEvY3NzZV9jb3ZpZF8xOV90aW1lX3Nlcmllcy90aW1lX3Nlcmllc18xOS1jb3ZpZC1SZWNvdmVyZWQuY3N2IikpICU+JQogIGdhdGhlcihkYXRlLCByZWNvdmVyZWQsIC1gUHJvdmluY2UvU3RhdGVgLCAtYENvdW50cnkvUmVnaW9uYCwgLUxhdCwgLUxvbmcpICU+JQogIG11dGF0ZShkYXRlID0gbWR5KGRhdGUpKQoKam9pbmVkIDwtIGNvbmZpcm1lZF90cyAlPiUKICBsZWZ0X2pvaW4oZGVhdGhzX3RzKSAlPiUKICBsZWZ0X2pvaW4ocmVjb3ZlcmVkX3RzKSAlPiUKICBnYXRoZXIoc3RhdCwgdmFsdWUsIGNvbmZpcm1lZCwgZGVhdGhzLCByZWNvdmVyZWQpICU+JQogIGFycmFuZ2UoYENvdW50cnkvUmVnaW9uYCwgYFByb3ZpbmNlL1N0YXRlYCwgZGF0ZSwgc3RhdCkKCmpvaW5lZCAlPiUgbXV0YXRlX2lmKGlzLmNoYXJhY3RlciwgYXMuZmFjdG9yKSAlPiUgc3VtbWFyeSgpCmBgYApgYGB7cn0KIycgUHJlcGFyZXMgZGF0YS5mcmFtZSBmb3IgcGxvdHRpbmcgYnkgYWdncmVnYXRpbmcsIHdlaWdodGluZywgcmVvcmRlcmluZywgCiMnIGFuZCBmaWx0ZXJpbmcgYnkgbG9jYXRpb24uCiMnCiMnIEBwYXJhbSBnZW9fbGV2ZWwgY29sdW1uIG5hbWUgdG8gYWdncmVnYXRlIChzdW0pIGJ5CiMnIEBwYXJhbSBkY3Jfd3RzIHdlaWdodHMgdG8gb3JkZXIgbG9jYXRpb25zOyB2ZWN0b3Igb2YgYyhkZWF0aHMsIGNvbmZpcm1lZCwgcmVjb3ZlcmVkKQojJyBAcGFyYW0gc2hvd190b3BfbiBzaG93IHRvcCBuIGxvY2F0aW9ucyBvcmRlcmVkIGJ5IGRjcl93dHMKIycKIycgQHJldHVybiBhbiBhZ2dyZWdhdGVkLCByZW9yZGVyZWQsIGFuZCBmaWx0ZXJlZCBieSBsb2NhdGlvbiBkYXRhLmZyYW1lCiMnCiMnIEBleGFtcGxlcwojJyBwbG90X3ByZXAoIkNvdW50cnkvUmVnaW9uIiwgYygxMDAsIDEsIC0xKSwgc2hvd190b3BfbiA9IDI1KQojJwojJyBAZXhwb3J0CnBsb3RfcHJlcCA8LSBmdW5jdGlvbihkZiwgZ2VvX2xldmVsID0gIkNvdW50cnkvUmVnaW9uIiwgZGNyX3d0cyA9IGMoMSwgMC4wMSwgMCksCiAgICAgICAgICAgICAgICAgICAgICBzaG93X3RvcCA9IDE6MTApIHsKICBkZiAlPiUKICAgIG11dGF0ZShsb2NhdGlvbiA9ICEhc3ltKGdlb19sZXZlbCkpICU+JQogICAgIyBhZ2dyZWdhdGUgYnkgbG9jYXRpb24KICAgIGdyb3VwX2J5KGxvY2F0aW9uLCBkYXRlLCBzdGF0KSAlPiUKICAgIHN1bW1hcmlzZSh2YWx1ZSA9IHN1bSh2YWx1ZSkpICU+JQogICAgdW5ncm91cCgpICU+JQogICAgIyBsb2NhdGlvbiBvcmRlcgogICAgZ3JvdXBfYnkobG9jYXRpb24pICU+JQogICAgbXV0YXRlKAogICAgICB0b3RhbF93dCA9CiAgICAgICAgdmFsdWVbd2hpY2goZGF0ZSA9PSBtYXgoZGF0ZSkgJiBzdGF0ID09ICJkZWF0aHMiKV0gKiBkY3Jfd3RzWzFdICsKICAgICAgICB2YWx1ZVt3aGljaChkYXRlID09IG1heChkYXRlKSAmIHN0YXQgPT0gImNvbmZpcm1lZCIpXSAqIGRjcl93dHNbMl0gKwogICAgICAgIHZhbHVlW3doaWNoKGRhdGUgPT0gbWF4KGRhdGUpICYgc3RhdCA9PSAicmVjb3ZlcmVkIildICogZGNyX3d0c1szXQogICAgKSAlPiUKICAgIHVuZ3JvdXAoKSAlPiUKICAgIG11dGF0ZSh0b3RhbF9yYW5rID0gZGVuc2VfcmFuayhkZXNjKHRvdGFsX3d0KSkpICU+JQogICAgIyBmaWx0ZXIgdG9wIG4KICAgIGZpbHRlcih0b3RhbF9yYW5rICVpbiUgc2hvd190b3ApICU+JQogICAgIyBvcmRlciBieSByYW5rCiAgICBtdXRhdGUobG9jYXRpb24gPSBmY3RfcmVvcmRlcihsb2NhdGlvbiwgdG90YWxfcmFuaykpIAp9CmBgYAoKIyBDcm9zcy1jb3VudHJ5IGNvbXBhcmlzb24gey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9CgpgYGB7cn0KIycgQ29udmVuaWVuY2UgZnVuY3Rpb24gdG8gcGxvdCBjb3VudHJ5IGNvbXBhcmlzb25zCnBsb3RfY291bnRyeV9jb21wcyA8LSBmdW5jdGlvbihkZikgewogIGRmICU+JSAKICAgIGdncGxvdChhZXMoZGF0ZSwgdmFsdWUsIGNvbG9yPWxvY2F0aW9uKSkgKwogICAgZ2VvbV9saW5lKCkgKyBmYWNldF93cmFwKHZhcnMoc3RhdCksIG5jb2w9MSwgc2NhbGVzID0gImZyZWVfeSIpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgeGxhYihlbGVtZW50X2JsYW5rKCkpCn0KYGBgCgoKIyMgVG9wIGNvdW50cmllcwoKYGBge3IgZmlnLmhlaWdodCA9IDh9CmpvaW5lZCAlPiUKICBwbG90X3ByZXAoIkNvdW50cnkvUmVnaW9uIikgJT4lCiAgcGxvdF9jb3VudHJ5X2NvbXBzKCkKYGBgCgojIyBleGNsIENoaW5hCgpgYGB7ciBmaWcuaGVpZ2h0ID0gOH0Kam9pbmVkICU+JQogIGZpbHRlcighYENvdW50cnkvUmVnaW9uYCAlaW4lICBjKCJDaGluYSIpKSAlPiUKICBwbG90X3ByZXAoIkNvdW50cnkvUmVnaW9uIikgJT4lCiAgcGxvdF9jb3VudHJ5X2NvbXBzKCkKYGBgCgojIyBleGNsIEl0YWx5LCBJcmFuLCBTb3V0aCBLb3JlYQoKYGBge3IgZmlnLmhlaWdodCA9IDh9CmpvaW5lZCAlPiUKICBmaWx0ZXIoIWBDb3VudHJ5L1JlZ2lvbmAgJWluJSAgYygiQ2hpbmEiLCAiSXRhbHkiLCAiSXJhbiIsICJLb3JlYSwgU291dGgiKSkgJT4lCiAgcGxvdF9wcmVwKCJDb3VudHJ5L1JlZ2lvbiIpICU+JQogIHBsb3RfY291bnRyeV9jb21wcygpCmBgYAoKIyMgZXhjbCBTcGFpbiwgRnJhbmNlCgpgYGB7ciBmaWcuaGVpZ2h0ID0gOH0Kam9pbmVkICU+JQogIGZpbHRlcighYENvdW50cnkvUmVnaW9uYCAlaW4lICBjKCJDaGluYSIsICJJdGFseSIsICJJcmFuIiwgIktvcmVhLCBTb3V0aCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNwYWluIiwgIkZyYW5jZSIpKSAlPiUKICBwbG90X3ByZXAoIkNvdW50cnkvUmVnaW9uIikgJT4lCiAgcGxvdF9jb3VudHJ5X2NvbXBzKCkKYGBgCgojIENvbXBhcmluZyBncm93dGggey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9CgpgYGB7cn0KIycgQ29udmVuaWVuY2UgZnVuY3Rpb24gdG8gcGxvdCBncm93dGggY29tcGFyaXNvbnMKcGxvdF9ncm93dGhfY29tcHMgPC0gZnVuY3Rpb24oZGYpIHsKICBkZiAlPiUKICAgIGdncGxvdChhZXMoZGF0ZSwgdmFsdWUpKSArIGdlb21fbGluZSgpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSwgdmp1c3QgPSAxKSkgKwogICAgZmFjZXRfd3JhcCgKICAgICAgdmFycyhsb2NhdGlvbiwgc3RhdCksIHNjYWxlcz0iZnJlZV95IiwgbmNvbCA9IDMsCiAgICAgIGxhYmVsbGVyPWxhYmVsX3dyYXBfZ2VuKG11bHRpX2xpbmU9RkFMU0UpKSArCiAgICB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb24gPSAiTm9uZSIpICsKICAgIHhsYWIoZWxlbWVudF9ibGFuaygpKQp9CmBgYAoKIyMgQnkgY291bnRyeQoKYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTE1fQpqb2luZWQgJT4lCiAgcGxvdF9wcmVwKCkgJT4lCiAgcGxvdF9ncm93dGhfY29tcHMoKQpgYGAKCiMjIENoaW5hCgpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9MTV9CmpvaW5lZCAlPiUKICBmaWx0ZXIoYENvdW50cnkvUmVnaW9uYCA9PSAiQ2hpbmEiKSAlPiUKICBwbG90X3ByZXAoIlByb3ZpbmNlL1N0YXRlIikgJT4lCiAgcGxvdF9ncm93dGhfY29tcHMoKQpgYGAKCiMjIFVTCgpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9MTh9CmpvaW5lZCAlPiUKICBmaWx0ZXIoYENvdW50cnkvUmVnaW9uYCA9PSAiVVMiKSAlPiUKICBwbG90X3ByZXAoIlByb3ZpbmNlL1N0YXRlIikgJT4lCiAgcGxvdF9ncm93dGhfY29tcHMoKQpgYGAKCiMjIENhbmFkYQoKYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTE1fQpqb2luZWQgJT4lCiAgZmlsdGVyKGBDb3VudHJ5L1JlZ2lvbmAgPT0gIkNhbmFkYSIpICU+JQogIHBsb3RfcHJlcCgiUHJvdmluY2UvU3RhdGUiKSAlPiUKICBwbG90X2dyb3d0aF9jb21wcygpCmBgYAoKIyMgQXVzdHJhbGlhCgpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9MTV9CmpvaW5lZCAlPiUKICBmaWx0ZXIoYENvdW50cnkvUmVnaW9uYCA9PSAiQXVzdHJhbGlhIikgJT4lCiAgcGxvdF9wcmVwKCJQcm92aW5jZS9TdGF0ZSIpICU+JQogIHBsb3RfZ3Jvd3RoX2NvbXBzKCkKYGBgCgojIFRvdGFsIGRlYXRocyBzaW5jZSBmaXJzdCBkZWF0aCB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30KYGBge3J9CnBsb3RfZGVhdGhzX3NpbmNlX2ZpcnN0IDwtIGZ1bmN0aW9uKGRmLCBkYXlzX3NpbmNlLCBpbml0X2RlYXRocyA9IDMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfdG9wID0gMToxMCkgewogIHhsYWIgPC0gcGFzdGUoIkRheXMgc2luY2UgZGVhdGhzID0iLCBpbml0X2RlYXRocykKICBkZiAlPiUKICAgIHBsb3RfcHJlcCgiQ291bnRyeS9SZWdpb24iLCBzaG93X3RvcCA9IHNob3dfdG9wKSAlPiUKICAgIGdyb3VwX2J5KGxvY2F0aW9uKSAlPiUKICAgIG11dGF0ZSh0b3RhbF9kZWF0aHMgPSB2YWx1ZVt3aGljaChkYXRlID09IG1heChkYXRlKSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGF0ID09ICJkZWF0aHMiKV1bMV0pICU+JQogICAgZmlsdGVyKHRvdGFsX2RlYXRocyA+IDApICU+JQogICAgbXV0YXRlKGluaXRfZGVhdGhfZGF0ZSA9IGRhdGVbd2hpY2godmFsdWUgPj0gaW5pdF9kZWF0aHMgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhdCA9PSAiZGVhdGhzIildWzFdKSAlPiUKICAgIG11dGF0ZSgKICAgICAgISFzeW0oeGxhYikgOj0gZGF0ZSAtIGluaXRfZGVhdGhfZGF0ZSkgJT4lCiAgICBmaWx0ZXIoc3RhdCA9PSAiZGVhdGhzIiAmIHZhbHVlID4gMCAmCiAgICAgICAgICAgICBiZXR3ZWVuKCEhc3ltKHhsYWIpLCAwLCBkYXlzX3NpbmNlKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKCEhc3ltKHhsYWIpLCB2YWx1ZSwgCiAgICAgICAgICAgICAgIGNvbG9yPWxvY2F0aW9uKSkgKyBnZW9tX2xpbmUoKSArCiAgICB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLAogICAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB5bGFiKCJUb3RhbCBkZWF0aHMiKQp9CmBgYAoKW0Rhc2hib2FyZCB2ZXJzaW9uXShodHRwczovL3JvYm9uLnNoaW55YXBwcy5pby9kZWF0aGNvbXAvKQoKIyMgMTAgZGF5cyBzaW5jZSBmaXJzdCBkZWF0aApgYGB7ciBmaWcud2lkdGg9OCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kam9pbmVkICU+JQogIHBsb3RfZGVhdGhzX3NpbmNlX2ZpcnN0KDEwKQpgYGAKCiMjIDMwIGRheXMgc2luY2UgZmlyc3QgZGVhdGgKYGBge3IgZmlnLndpZHRoPTgsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmpvaW5lZCAlPiUKICBwbG90X2RlYXRoc19zaW5jZV9maXJzdCgzMCkKYGBgCgojIyA2MCBkYXlzIHNpbmNlIGZpcnN0IGRlYXRoCmBgYHtyIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpqb2luZWQgJT4lCiAgcGxvdF9kZWF0aHNfc2luY2VfZmlyc3QoNjApCmBgYAoKIyBEb3VibGUgZGF5cwoKIyMgRG91YmxlIGRheXMgYnkgY291bnRyeS9zdGF0IHsudGFic2V0IC50YWJzZXQtZmFkZSAudGFic2V0LXBpbGxzfQoKIyMjIFRvcCAxMCBjb3VudHJpZXMKCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmpvaW5lZCAlPiUKICBwbG90X3ByZXAoZGNyX3d0cyA9IGMoMTAwLCAwLjEsIDApLCBzaG93X3RvcCA9IDE6MTApICU+JQogIGdyb3VwX2J5KGxvY2F0aW9uLCBzdGF0KSAlPiUKICBhcnJhbmdlKGRhdGUpICU+JQogIG11dGF0ZSgKICAgIGBEYXlzIHRvIGRvdWJsZWAgPSBpZl9lbHNlKAogICAgICB2YWx1ZSA8IDEwLCBhcy5kaWZmdGltZSgiIiwgdW5pdHM9ImRheXMiKSwKICAgICAgZGF0ZSAtIGRhdGVbc2FwcGx5KHZhbHVlLCBmdW5jdGlvbih4KSB7IHdoaWNoLm1pbihhYnMoeCAvIDIgLSB2YWx1ZSkpIH0pXSkKICApICU+JQogIGdncGxvdChhZXMoZGF0ZSwgYERheXMgdG8gZG91YmxlYCwgY29sb3I9c3RhdCkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9zbW9vdGgoKSArCiAgI2ZhY2V0X3dyYXAodmFycyhsb2NhdGlvbiksIG5jb2wgPSAzKSArIAogIGZhY2V0X2dyaWQodmFycyhsb2NhdGlvbiksIHZhcnMoc3RhdCksIHNjYWxlcz0iZnJlZSIpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsIGxlZ2VuZC50aXRsZT1lbGVtZW50X2JsYW5rKCkpICsKICB4bGFiKGVsZW1lbnRfYmxhbmsoKSkKYGBgCgojIyMgZXhjbCBDaGluYQoKYGBge3IgZmlnLmhlaWdodD0xMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kam9pbmVkICU+JQogIGZpbHRlcihgQ291bnRyeS9SZWdpb25gICE9ICJDaGluYSIpICU+JQogIHBsb3RfcHJlcChkY3Jfd3RzID0gYygxMDAsIDAuMSwgMCksIHNob3dfdG9wID0gMToxMCkgJT4lCiAgZ3JvdXBfYnkobG9jYXRpb24sIHN0YXQpICU+JQogIGFycmFuZ2UoZGF0ZSkgJT4lCiAgbXV0YXRlKAogICAgYERheXMgdG8gZG91YmxlYCA9IGlmX2Vsc2UoCiAgICAgIHZhbHVlIDwgMTAsIGFzLmRpZmZ0aW1lKCIiLCB1bml0cz0iZGF5cyIpLAogICAgICBkYXRlIC0gZGF0ZVtzYXBwbHkodmFsdWUsIGZ1bmN0aW9uKHgpIHsgd2hpY2gubWluKGFicyh4IC8gMiAtIHZhbHVlKSkgfSldKQogICkgJT4lCiAgZ2dwbG90KGFlcyhkYXRlLCBgRGF5cyB0byBkb3VibGVgLCBjb2xvcj1zdGF0KSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX3Ntb290aCgpICsKICAjZmFjZXRfd3JhcCh2YXJzKGxvY2F0aW9uKSwgbmNvbCA9IDMpICsgCiAgZmFjZXRfZ3JpZCh2YXJzKGxvY2F0aW9uKSwgdmFycyhzdGF0KSwgc2NhbGVzPSJmcmVlIikgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwgbGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSkgKwogIHhsYWIoZWxlbWVudF9ibGFuaygpKQpgYGAKCiMjICBEYXlzIHRvIGRvdWJsZSBkZWF0aHMgc2luY2UgbnRoIGRlYXRoIHsudGFic2V0IC50YWJzZXQtZmFkZSAudGFic2V0LXBpbGxzfQoKaC90OiBbXEBsb2VzZXJqb2huXShodHRwczovL3R3aXR0ZXIuY29tL2xvZXNlcmpvaG4pCgpbRGFzaGJvYXJkIHZlcnNpb25dKGh0dHBzOi8vcm9ib24uc2hpbnlhcHBzLmlvL2RheXMyZG91YmxlLykKCiMjIyBDb3VudHJ5L1JlZ2lvbiBsZXZlbAoKYGBge3IgZmlnLndpZHRoPTksIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm5fZGVhdGhzIDwtIDEwCm1pbl9kZWF0aHMgPC0gbl9kZWF0aHMgKiAyCnRvcF9uX2NvdW50cmllcyA8LSAxOjIwCnRydW5jX3RvX24gPC0gMgpnZW9fbGV2ZWwgPSAiQ291bnRyeS9SZWdpb24iCgojIFRPRE8ocm9ib3Rvbik6IFJlZmFjdG9yIGludG8gcHJlcCBhbmQgcGxvdCBmdW5jdGlvbnMKam9pbmVkICU+JQogIAogICMgcGxvdCBwcmVwIGF0IGNvdW50cnkgbGV2ZWwKICBwbG90X3ByZXAoZ2VvX2xldmVsLCBzaG93X3RvcCA9IHRvcF9uX2NvdW50cmllcykgJT4lCiAgCiAgIyBsaW1pdCB0byBkZWF0aHMKICBmaWx0ZXIoc3RhdCA9PSAiZGVhdGhzIikgJT4lCiAgCiAgIyBkcm9wIGNvdW50cmllcyB3aXRoIGxlc3MgdGhhbiBtaW5fZGVhdGhzCiAgZ3JvdXBfYnkobG9jYXRpb24pICU+JQogIGZpbHRlcih2YWx1ZVt3aGljaC5tYXgoZGF0ZSldID49IG1pbl9kZWF0aHMpICU+JQogIHVuZ3JvdXAoKSAlPiUKICAKICAjIGdldCB0aGUgZmlyc3QgZGF0ZSB3aXRoIG1vcmUgdGhhbiBvciBlcXVhbCB0byBuX2RlYXRocwogIGdyb3VwX2J5KGxvY2F0aW9uKSAlPiUKICBtdXRhdGUobnRoX2RlYXRoX2RhdGUgPSBkYXRlW3doaWNoKHZhbHVlID49IG5fZGVhdGhzKSAtIDFdWzFdKSAlPiUKICBtdXRhdGUoYERheXMgc2luY2UgbnRoIGRlYXRoYCA9IGRhdGUgLSBudGhfZGVhdGhfZGF0ZSkgJT4lCiAgCiAgIyBEcm9wIGRheXMgYmVmb3JlIG50aCBkZWF0aHMgZGF0ZQogIGZpbHRlcihgRGF5cyBzaW5jZSBudGggZGVhdGhgID4gMCkgJT4lCiAgCiAgIyBDb21wdXRlIERheXMgdG8gRG91YmxlCiAgZ3JvdXBfYnkobG9jYXRpb24pICU+JQogIAogIG11dGF0ZSgKICAgICMgc2V0IGRheXMgdG8gZG91YmxlCiAgICBkb3VibGVfaWR4ID0gc2FwcGx5KHZhbHVlLCBGVU49ZnVuY3Rpb24oeCkgeyBtYXgod2hpY2godmFsdWUgPD0geC8yKSkgfSksCiAgICBgRGF5cyB0byBkb3VibGVgID0gZGF0ZSAtIGRhdGVbZG91YmxlX2lkeF0KICApICU+JQogIGZpbHRlcighaXMubmEoYERheXMgdG8gZG91YmxlYCkpICU+JQogICMjIHBsb3QKICAjIHRydW5jYXRlIHRvIHNlY29uZCBsb25nZXN0IHRpbWUgc2VyaWVzCiAgZ3JvdXBfYnkobG9jYXRpb24pICU+JQogIG11dGF0ZShtYXhfZGF5c19zaW5jZV9udGhfZGVhdGggPSBtYXgoYERheXMgc2luY2UgbnRoIGRlYXRoYCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUobWF4X2RheXNfcmFuayA9IGRlbnNlX3JhbmsoZGVzYyhtYXhfZGF5c19zaW5jZV9udGhfZGVhdGgpKSkgJT4lCiAgZmlsdGVyKGBEYXlzIHNpbmNlIG50aCBkZWF0aGAgPD0gbWF4KGBEYXlzIHNpbmNlIG50aCBkZWF0aGBbbWF4X2RheXNfcmFuayA9PSB0cnVuY190b19uXSkpICU+JQogIGdyb3VwX2J5KGxvY2F0aW9uKSAlPiUKICBtdXRhdGUodGltZV9wb2ludHMgPSBuKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBmaWx0ZXIodGltZV9wb2ludHMgPiAyKSAlPiUKICBnZ3Bsb3QoYWVzKGBEYXlzIHNpbmNlIG50aCBkZWF0aGAsIGBEYXlzIHRvIGRvdWJsZWAsIGNvbG9yPWxvY2F0aW9uKSkgKwogIGdlb21fbGluZShzdGF0ID0gInNtb290aCIsIG1ldGhvZCA9ICJhdXRvIiwgc2UgPSBGKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMiwgc2l6ZSA9IDEpICsKICB4bGFiKHBhc3RlKCJkYXlzIHNpbmNlIGRlYXRocyA9Iiwgbl9kZWF0aHMpKSAtPiBwCiAgZ2dwbG90bHkocCkKYGBgCgojIyMgTG9uZyB2aWV3CgpgYGB7ciBmaWcud2lkdGggPSA5LCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9IEZBTFNFfQpuX2RlYXRocyA8LSAxMAptaW5fZGVhdGhzIDwtIG5fZGVhdGhzICogMgp0b3Bfbl9jb3VudHJpZXMgPC0gMToxNQp0cnVuY190b19uIDwtIDEKZ2VvX2xldmVsID0gIkNvdW50cnkvUmVnaW9uIgoKIyBUT0RPKHJvYm90b24pOiBSZWZhY3RvciBpbnRvIHByZXAgYW5kIHBsb3QgZnVuY3Rpb25zCmpvaW5lZCAlPiUKICAjZmlsdGVyKCFpcy5uYShgUHJvdmluY2UvU3RhdGVgKSAmIGBQcm92aW5jZS9TdGF0ZWAgIT0gYENvdW50cnkvUmVnaW9uYCkgJT4lCiAgIAogICMgcGxvdCBwcmVwIGF0IGNvdW50cnkgbGV2ZWwKICBwbG90X3ByZXAoZ2VvX2xldmVsLCBzaG93X3RvcCA9IHRvcF9uX2NvdW50cmllcykgJT4lCiAgCiAgIyBsaW1pdCB0byBkZWF0aHMKICBmaWx0ZXIoc3RhdCA9PSAiZGVhdGhzIikgJT4lCiAgCiAgIyBkcm9wIGNvdW50cmllcyB3aXRoIGxlc3MgdGhhbiBtaW5fZGVhdGhzCiAgZ3JvdXBfYnkobG9jYXRpb24pICU+JQogIGZpbHRlcih2YWx1ZVt3aGljaC5tYXgoZGF0ZSldID49IG1pbl9kZWF0aHMpICU+JQogIHVuZ3JvdXAoKSAlPiUKICAKICAjIGdldCB0aGUgZmlyc3QgZGF0ZSB3aXRoIG1vcmUgdGhhbiBvciBlcXVhbCB0byBuX2RlYXRocwogIGdyb3VwX2J5KGxvY2F0aW9uKSAlPiUKICBtdXRhdGUobnRoX2RlYXRoX2RhdGUgPSBkYXRlW3doaWNoKHZhbHVlID49IG5fZGVhdGhzKSAtIDFdWzFdKSAlPiUKICBtdXRhdGUoYERheXMgc2luY2UgbnRoIGRlYXRoYCA9IGRhdGUgLSBudGhfZGVhdGhfZGF0ZSkgJT4lCiAgCiAgIyBEcm9wIGRheXMgYmVmb3JlIG50aCBkZWF0aHMgZGF0ZQogIGZpbHRlcihgRGF5cyBzaW5jZSBudGggZGVhdGhgID4gMCkgJT4lCiAgCiAgIyBDb21wdXRlIERheXMgdG8gRG91YmxlCiAgZ3JvdXBfYnkobG9jYXRpb24pICU+JQogIAogIG11dGF0ZSgKICAgICMgc2V0IGRheXMgdG8gZG91YmxlCiAgICBkb3VibGVfaWR4ID0gc2FwcGx5KHZhbHVlLCBGVU4gPSBmdW5jdGlvbih4KSB7IG1heCh3aGljaCh2YWx1ZSA8PSB4LzIpKSB9KSwKICAgIGBEYXlzIHRvIGRvdWJsZWAgPSBkYXRlIC0gZGF0ZVtkb3VibGVfaWR4XQogICkgJT4lCiAgZmlsdGVyKCFpcy5uYShgRGF5cyB0byBkb3VibGVgKSkgJT4lCiAgIyMgcGxvdAogICMgdHJ1bmNhdGUgdG8gc2Vjb25kIGxvbmdlc3QgdGltZSBzZXJpZXMKICBncm91cF9ieShsb2NhdGlvbikgJT4lCiAgbXV0YXRlKG1heF9kYXlzX3NpbmNlX250aF9kZWF0aCA9IG1heChgRGF5cyBzaW5jZSBudGggZGVhdGhgKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZShtYXhfZGF5c19yYW5rID0gZGVuc2VfcmFuayhkZXNjKG1heF9kYXlzX3NpbmNlX250aF9kZWF0aCkpKSAlPiUKICBmaWx0ZXIoYERheXMgc2luY2UgbnRoIGRlYXRoYCA8PSBtYXgoYERheXMgc2luY2UgbnRoIGRlYXRoYFttYXhfZGF5c19yYW5rID09IHRydW5jX3RvX25dKSkgJT4lCiAgZ3JvdXBfYnkobG9jYXRpb24pICU+JQogIG11dGF0ZSh0aW1lX3BvaW50cyA9IG4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGZpbHRlcih0aW1lX3BvaW50cyA+IDIpICU+JQogIGdncGxvdChhZXMoYERheXMgc2luY2UgbnRoIGRlYXRoYCwgYERheXMgdG8gZG91YmxlYCwKICAgICAgICAgICAgIGNvbG9yID0gbG9jYXRpb24pKSArCiAgZ2VvbV9saW5lKHN0YXQgPSAic21vb3RoIiwgbWV0aG9kID0gImF1dG8iLCBzZSA9IEYpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4yLCBzaXplID0gMSkgKwogIHhsYWIocGFzdGUoImRheXMgc2luY2UgZGVhdGhzID49Iiwgbl9kZWF0aHMpKSAtPiBwCiAgZ2dwbG90bHkocCkgCmBgYAoKIyBQcm90b3R5cGUKCmBgYHtyfQptaW5fdG90YWwgPC0gMTAKbWluX3N0YXQgPC0gICJkZWF0aHMiCmdlb19sZXZlbCA8LSAiQ291bnRyeS9SZWdpb24iCm1heF9kYXlzX3NpbmNlIDwtIDIwCm1pbl90b3RhbCA8LSA1MAoKCmpvaW5lZCAlPiUKICByZW5hbWUodG90YWwgPSB2YWx1ZSkgJT4lCiAgbXV0YXRlKGxvY2F0aW9uID0gISFzeW0oZ2VvX2xldmVsKSkgJT4lCiAgIyBhZ2dyZWdhdGUKICBncm91cF9ieShsb2NhdGlvbiwgc3RhdCwgZGF0ZSkgJT4lCiAgc3VtbWFyaXNlKHRvdGFsID0gc3VtKHRvdGFsKSkgJT4lCiAgIyBhZGQgY2ZyCiAgYmluZF9yb3dzKAogICAgKC4pICU+JQogICAgICBzZWxlY3QobG9jYXRpb24sIHN0YXQsIGRhdGUsIHRvdGFsKSAlPiUKICAgICAgc3ByZWFkKHN0YXQsIHRvdGFsKSAlPiUKICAgICAgbXV0YXRlKGNmciA9IGRlYXRocy9jb25maXJtZWQpICU+JQogICAgICBnYXRoZXIoc3RhdCwgdG90YWwsIGNvbmZpcm1lZCwgZGVhdGhzLCByZWNvdmVyZWQsIGNmcikgJT4lCiAgICAgIGZpbHRlcihzdGF0ID09ICJjZnIiKSkgJT4lCiAgIyBnZXQgbWF4X3RvdGFsIGFuZCBmaXJzdF9kYXRlIHBlciBsb2NhdGlvbi9zdGF0CiAgZ3JvdXBfYnkobG9jYXRpb24sIHN0YXQpICU+JQogIG11dGF0ZShtYXhfdG90YWwgPSBtYXgodG90YWwpLAogICAgICAgICBmaXJzdF9kYXRlID0gbWluKGRhdGVbdG90YWwgPj0gbWluX3RvdGFsXSkpICU+JQogIGdyb3VwX2J5KGxvY2F0aW9uKSAlPiUKICAjIGRyb3AgZWFybGllciBkYXRlcwogIGZpbHRlcihkYXRlID49IGZpcnN0X2RhdGVbc3RhdCA9PSBtaW5fc3RhdF0pICU+JQogICMgcmVjZW50ZXIgZGF0ZXMKICBtdXRhdGUoZGF5c19zaW5jZSA9IGRhdGUgLSBmaXJzdF9kYXRlW3N0YXQgPT0gbWluX3N0YXRdKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgIyBjYWxjIGRvdWJsZV9kYXlzCiAgZ3JvdXBfYnkobG9jYXRpb24sIHN0YXQpICU+JQogIG11dGF0ZSgKICAgIGRvdWJsZV9kYXlzID0gZGF0ZSAtCiAgICAgIGRhdGVbc2FwcGx5KAogICAgICAgIHRvdGFsLCBGVU4gPSBmdW5jdGlvbih4KSB7IG1heCh3aGljaCh0b3RhbCA8PSB4LzIpKSB9KV0pICU+JQogIHVuZ3JvdXAoKSAlPiUKICBnYXRoZXIodmFsdWVfdHlwZSwgdmFsdWUsIHRvdGFsLCBkb3VibGVfZGF5cykgJT4lCiAgIyMgcGxvdHRpbmcKICAjIHRydW5jYXRlIGRheXNfc2luY2UKICBmaWx0ZXIoZGF5c19zaW5jZSA8PSBtYXhfZGF5c19zaW5jZSkgJT4lCiAgIyBkcm9wcGluZyBzbWFsbCBsb2NhdGlvbnMKICBncm91cF9ieShsb2NhdGlvbikgJT4lCiAgZmlsdGVyKG1heF90b3RhbFtzdGF0ID09IG1pbl9zdGF0XVsxXSA+PSBtaW5fdG90YWwpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBnZ3Bsb3QoYWVzKGRheXNfc2luY2UsIHZhbHVlLCBjb2xvciA9IGxvY2F0aW9uKSkgKwogIGdlb21fbGluZSgpICsKICBmYWNldF93cmFwKHZhcnMoc3RhdCwgdmFsdWVfdHlwZSksIHNjYWxlcyA9ICJmcmVlIiwgbmNvbD0yLAogICAgICAgICAgICAgbGFiZWxsZXIgPSBsYWJlbF93cmFwX2dlbihtdWx0aV9saW5lID0gRkFMU0UpKQpgYGAKCg==